#ifndef _INI_FILE_H_
#define _INI_FILE_H_

#include <iostream>
#include <string>
#include <sstream>
#include <assert.h>
#include "IniInfo.h"


namespace inilib
{

	/**
	 * Type of validation that is applied on the given ini file.
	 */
	enum ValidationType
	{
		/**
		 * Strict means that any sections or options that weren't specified in the IniFormat
		 * will result in error.
		 */
		STRICT,

		/**
		 * Relaxed will treat any unknown sections or options as strings (OptionInfoString)
		 * so they will have practically no restrictions on their values.
		 */
		RELAXED
	};

	/**
	 * 
	 */
	class IniFile
	{
	public:

		~IniFile ();

		/**
		 * Returns value of given option and converts value to type T.
		 * Type T MUST implement "std::istream & operator>> ( std::istream & is, T & value )"
		 * in the namespace inilib.
		 */
		template <typename T>
		T GetValue (
			const std::string & sectionName,
			const std::string & optionName
		) const;

		/**
		 * Returns values of given option and converts value to type T.
		 * Type T MUST implement "std::istream & operator>> ( std::istream & is, T & value )"
		 * in the namespace inilib.
		 */
		template <typename T>
		std::vector< T > GetValues (
			const std::string & sectionName,
			const std::string & optionName
		) const;

		/**
		 * Sets value for given option. Type T MUST implement his own "operator<<"
		 * (it doesn't have to be in namespace inilib) so that he can be converted to string.
		 */
		template <typename T>
		void SetValue (
			const std::string & sectionName,
			const std::string & optionName,
			const T & value
		);

		/**
		 * Sets values for given option. Type T MUST implement his own "operator<<"
		 * (it doesn't have to be in namespace inilib) so that he can be converted to string.
		 */
		template <typename T>
		void SetValues (
			const std::string & sectionName,
			const std::string & optionName,
			const std::vector< T > & values
		);

		/**
		 * Save the ini file to the string (including all comments).
		 */
		void SaveTo ( std::string & s ) const;

		/**
		 * Save the ini file to the stream (including all comments).
		 */
		void SaveTo ( std::ostream & s ) const;

		/**
		 * Creates representation of ini file from data-string.
		 */
		static IniFile * Create (
			IniInfo * format,
			ValidationType type,
			const std::string & fileData
		);

		/**
		 * Creates representation of ini file from stream.
		 */
		static IniFile * Create (
			IniInfo * format,
			ValidationType type,
			std::istream & stream
		);

	private:

		static const char DEFAULT_SEPARATOR = ',';

		explicit IniFile ( IniInfo * format, ValidationType type, std::istream & is );

		// ----------------------------------------------------------
		// Helper methods
		// ----------------------------------------------------------
		
		bool IsEmpty ( const std::string & line ) const;
		bool IsComment ( const std::string & line ) const;
		bool IsNewSection ( const std::string & line ) const;
		bool IsValidChar ( char c ) const;
		bool IsValidIdentif ( const std::string & str ) const;
		std::string RemoveSpaces ( const std::string & in ) const;
		std::string IniFile::Trim ( const std::string & str ) const;

		/**
		 * Returns true if there exists section with name "sectionName" in the ini file.
		 * Sections that are not mandatory and are not present in the ini file will also
		 * exist because they will be added automatically with their default value(s).
		 */
		bool ExistSection ( const std::string & sectionName ) const;

		/**
		 * Returns true if there exists option with name "sectionName" in the section called
		 * "sectionName". Options that are not mandatory and are not present in the ini file
		 * will also exist because they will be added automatically with their default value(s).
		 */
		bool ExistOption ( const std::string & sectionName, const std::string & optionName ) const;

		// ----------------------------------------------------------
		// Parse methods
		// ----------------------------------------------------------

		/**
		 * Get comment on the end of the line and return the remainder of string.
		 */
		void GetComment (
			const std::string & line,
			std::string & remainder,
			std::string & comment
		) const;

		void ParseSection (
			const std::string & line,
			int lineNumber,
			std::string & sectionName,
			std::string & sectionComment
		) const;

		void ParseOption (
			const std::string & line,
			int lineNumber,
			std::string & optionName,
			std::string & values,
			std::string & optionComment
		) const;

		/*
		 * Should be called if it is indeed a reference.
		 */
		void ParseReference (
			const std::string & line,
			std::string & sectionName,
			std::string & optionName
		) const;

		// ----------------------------------------------------------
		// Add
		// ----------------------------------------------------------

		void AddNewSection (
			const std::string & sectionName,
			const std::string & comment,
			int lineNumber,
			bool fromIni
		);

		void AddNewOption (
			const std::string & sectionName,
			const std::string & optionName,
			const std::string & values,
			const std::string & comment,
			int lineNumber,
			bool fromIni
		);

		void AddNewOption (
			const std::string & sectionName,
			const std::string & optionName,
			const std::vector< std::string > & values,
			const std::string & comment,
			OptionType * optionInfo,
			int lineNumber,
			bool fromIni
		);

		/**
		 * Validates section after all options have been added so the only
		 * thing that remains to be validated is whether or not
		 * all the options have been set in the ini file.
		 */
		void ValidateSection ( const std::string & sectionName ) const;

		void CheckMandatory ();

		/** Resets status of all options. */
		void ResetStatus ();

		/** Validates all references from ini file. */
		void ValidateReferences ();

		class Option;
		class Section;

		void ValidateOptionRef ( Option & option );

		// ----------------------------------------------------------
		// Get/Set support methods
		// ----------------------------------------------------------

		/**
		 * Internal get value that is responsible for converting any other value to string.
		 */
		template <typename T>
		T GetValue ( const std::string & value ) const;

		template <typename T>
		std::string ToString ( const T & value ) const;
		
		Section & GetSection ( const std::string & sectionName ) const;

		IniInfo::SectionInfo & GetSectionInfo ( const std::string & sectionName ) const;

		Option & GetOption (
			const std::string & sectionName,
			const std::string & optionName
		) const;

		OptionType * GetOptionType (
			const std::string & sectionName,
			const std::string & optionName
		) ;

		/**
		 * Get all values (including those from references) of given option.
		 */
		void GetAllValues (
			Option & option,
			std::vector< std::string > & values
		) const;
		
		// ----------------------------------------------------------
		// Interval classes
		// ----------------------------------------------------------

		enum DFSStatus
		{
			NOT_VISITED,
			IN_PROGRESS,
			FINISHED
		};

		class Option
		{
		public:

			Option (
				const std::string & optionName,
				const std::string & sectionName,
				const std::string & comment,
				char separator,
				bool fromIni
			);

			bool IsFromIni () const;
			char GetSeparator () const;
			const std::string & GetComment () const;
			const std::string & GetSectionName () const;
			const std::string & GetOptionName () const;
			const std::vector< std::string > & GetAllValues () const;
			const std::vector< std::string > & GetValues () const;
			const std::vector< std::string > & GetReferences () const;

			/**
			 * All value changes should be done via this method.
			 */
			void SetValues ( const std::vector< std::string > & values );

			/** Special variables for references validation. */
			DFSStatus m_Status;

		private:
			std::vector< std::string > m_AllValues;
			std::vector< std::string > m_Values;
			std::vector< std::string > m_References;

			std::string m_OptionName;
			std::string m_SectionName;
			std::string m_Comment;

			/** True if it has been loaded from the ini file. */
			bool m_FromIni;
			char m_Separator;

			bool IsReference ( const std::string & str ) const;
		};

		/** This is data pure class so all data are public. */
		class Section
		{
		public:

			explicit Section () {}
			explicit Section (
				const std::string & name,
				const std::string & comment,
				bool fromIni
			)
			{
				m_Name = name;
				m_Comment = comment;
				m_FromIni = fromIni;
			}

			/** True if it has been loaded from the ini file. */
			bool m_FromIni;

			std::string m_Name;
			std::string m_Comment;

			/** Option names in the order which they were loaded.  */
			std::vector< std::string > m_OptionsOrder;

			std::map< std::string, Option > m_Options;
		};

		// ----------------------------------------------------------
		// Data
		// ----------------------------------------------------------

		ValidationType m_ValidType;
		IniInfo * m_Format;

		/** Section names in the order which they were loaded. */
		std::vector< std::string > m_SectionsOrder;

		mutable std::map< std::string, Section > m_Sections;
	};

	#include "IniFile.inl"

}

#endif
